package com.monitor.rest;

import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import com.monitor.gray.Gray;
import com.monitor.gray.GrayUser;
import com.monitor.gray.GrayUtil;
import com.monitor.log.biz.core.BizLogData;
import com.monitor.log.biz.core.BizLogTraceIdUtil;
import com.ms.msclient.MsClientResponse;
import com.system.comm.utils.FrameJsonUtil;
import com.system.comm.utils.FrameMapUtil;
import com.system.comm.utils.FrameStringUtil;
import com.system.handle.model.ResponseCode;
import com.system.handle.model.ResponseFrame;

/**
 * 服务调度
 * 	V1.0.1：增加将响应的body转换成指定对象的功能
 * @author yuejing
 * @date 2019年3月31日 下午9:17:22
 * @version V1.0.1
 */
@Component
public class RestUtil {

	private static final Logger LOGGER = LoggerFactory.getLogger(RestUtil.class);
	@Autowired
	private LoadBalancerClient loadBalancer;
	@Autowired
	private RestTemplate restTemplate;

	public String getRestUrl(String serviceId, String fallbackUri) {
		if (FrameStringUtil.isEmpty(serviceId)) {
			return URI.create(fallbackUri).toString();
		}
		URI uri = null;
		try {
			ServiceInstance instance = loadBalancer.choose(serviceId);
			uri = instance.getUri();
		} catch (RuntimeException e) {
			if (FrameStringUtil.isNotEmpty(fallbackUri)) {
				uri = URI.create(fallbackUri);
			}
		}
		return uri != null ? uri.toString() : null;
	}
	public ServiceInstance getServiceInstance(String serviceId) {
		ServiceInstance instance = loadBalancer.choose(serviceId);
		return instance;
	}

	/**
	 * from data 发送请求
	 * @param serviceId
	 * @param url
	 * @param params
	 * @param bodyClazz
	 * @param fallbackUri
	 * @return
	 */
	public ResponseFrame request(String serviceId, String url, Map<String, Object> params) {
		long begin = System.currentTimeMillis();
		String resResult = null;
		// 处理灰度服务的逻辑
		serviceId = handleGray(serviceId, params);
		// 获取访问服务的前缀地址
		//String baseUrl = getRestUrl(serviceId, fallbackUri);
		//ServiceInstance instance = getServiceInstance(serviceId);
		String baseUrl = "http://" + serviceId;
		try {
			HttpHeaders headers = new HttpHeaders();
			headers.add(BizLogData.MS_TRACE_ID, BizLogTraceIdUtil.getTraceId());
			// 请勿轻易改变此提交方式，大部分的情况下，提交方式都是表单提交
			headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
			// 封装参数，千万不要替换为Map与HashMap，否则参数无法传递
			MultiValueMap<String, String> paramsMvm = new LinkedMultiValueMap<String, String>();
			Iterator<Entry<String, Object>> entryKeyIterator = params.entrySet().iterator();
			while (entryKeyIterator.hasNext()) {
				Entry<String, Object> e = entryKeyIterator.next();
				String value = null;
				if(e.getValue() != null) {
					value = e.getValue().toString();
				}
				paramsMvm.add(e.getKey(), value);
			}
			HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(paramsMvm, headers);
			resResult = restTemplate.postForObject(baseUrl + url, requestEntity, String.class);
			ResponseFrame frame = FrameJsonUtil.toObject(resResult, ResponseFrame.class);
			/*if (frame.getCode() == ResponseCode.SUCC.getCode() && bodyClazz != null) {
				frame.convertBody(bodyClazz);
			}*/
			/*if(ResponseCode.SUCC.getCode() != frame.getCode()) {
				String remark = "接口响应异常!";
				saveOptimizeLog(serviceId, url, params, begin, null, null, resResult, remark);
			}*/
			return frame;
		} catch (Exception e) {
			ResponseFrame frame = new ResponseFrame(ResponseCode.SERVER_ERROR);
			frame.setRequestParams(FrameJsonUtil.toString(params));
			frame.setExceptionInfo(e.getMessage());
			e.printStackTrace();
			return frame;
		} finally {
			requestFinally(begin, serviceId, url, params, resResult);
		}
	}

	/**
	 * 发送请求
	 * @param serviceId
	 * @param url
	 * @param params
	 * @return
	 */
	public ResponseFrame requestJsonBody(String serviceId, String url, Map<String, Object> params) {
		long begin = System.currentTimeMillis();
		String resResult = null;
		// 处理灰度服务的逻辑
		serviceId = handleGray(serviceId, params);
		// 获取访问服务的前缀地址
		//String baseUrl = getRestUrl(serviceId, fallbackUri);
		//ServiceInstance instance = getServiceInstance(serviceId);
		String baseUrl = "http://" + serviceId;
		try {
			HttpHeaders headers = new HttpHeaders();
			headers.add(BizLogData.MS_TRACE_ID, BizLogTraceIdUtil.getTraceId());
			// 请勿轻易改变此提交方式，大部分的情况下，提交方式都是表单提交
			headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
			String paramString = FrameJsonUtil.toString(params);
			@SuppressWarnings({ "rawtypes", "unchecked" })
			HttpEntity requestEntity = new HttpEntity(paramString, headers);
			resResult = restTemplate.postForObject(baseUrl + url, requestEntity, String.class);
			ResponseFrame frame = FrameJsonUtil.toObject(resResult, ResponseFrame.class);
			/*if (frame.getCode() == ResponseCode.SUCC.getCode() && bodyClazz != null) {
				frame.convertBody(bodyClazz);
			}*/
			/*if(ResponseCode.SUCC.getCode() != frame.getCode()) {
				String remark = "接口响应异常!";
				saveOptimizeLog(serviceId, url, params, begin, null, null, resResult, remark);
			}*/
			return frame;
		} catch (Exception e) {
			ResponseFrame frame = new ResponseFrame(ResponseCode.SERVER_ERROR);
			frame.setRequestParams(FrameJsonUtil.toString(params));
			frame.setExceptionInfo(e.getMessage());
			e.printStackTrace();
			return frame;
		} finally {
			requestFinally(begin, serviceId, url, params, resResult);
		}
	}

	/**
	 * from data 发送请求
	 * @param serviceId
	 * @param url
	 * @param params
	 * @param responseClazz
	 * @param responsePageClazz
	 * @return
	 */
	public <T> MsClientResponse<T> requestMsClient(String serviceId, String url, Map<String, Object> params, Class<?> responseClazz, Class<?> responsePageClazz) {
		long begin = System.currentTimeMillis();
		String resResult = null;
		// 处理灰度服务的逻辑
		serviceId = handleGray(serviceId, params);
		// 获取访问服务的前缀地址
		//String baseUrl = getRestUrl(serviceId, fallbackUri);
		//ServiceInstance instance = getServiceInstance(serviceId);
		String baseUrl = "http://" + serviceId;
		try {
			HttpHeaders headers = new HttpHeaders();
			headers.add(BizLogData.MS_TRACE_ID, BizLogTraceIdUtil.getTraceId());
			// 请勿轻易改变此提交方式，大部分的情况下，提交方式都是表单提交
			headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
			// 封装参数，千万不要替换为Map与HashMap，否则参数无法传递
			MultiValueMap<String, String> paramsMvm = new LinkedMultiValueMap<String, String>();
			Iterator<Entry<String, Object>> entryKeyIterator = params.entrySet().iterator();
			while (entryKeyIterator.hasNext()) {
				Entry<String, Object> e = entryKeyIterator.next();
				String value = null;
				if(e.getValue() != null) {
					value = e.getValue().toString();
				}
				paramsMvm.add(e.getKey(), value);
			}
			HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(paramsMvm, headers);
			//resResult = restTemplate.postForObject(baseUrl + url, requestEntity, String.class);
			//MsClientResponse<T> frame = FrameJsonUtil.toObject(resResult, MsClientResponse.class);
			@SuppressWarnings("unchecked")
			MsClientResponse<T> frame = restTemplate.postForObject(baseUrl + url, requestEntity, MsClientResponse.class);
			if (frame.getCode() == ResponseCode.SUCC.getCode() && responseClazz != null) {
				frame.convertBody(responseClazz, responsePageClazz);
			}
			/*if(ResponseCode.SUCC.getCode() != frame.getCode()) {
				String remark = "接口响应异常!";
				saveOptimizeLog(serviceId, url, params, begin, null, null, resResult, remark);
			}*/
			return frame;
		} catch (Exception e) {
			MsClientResponse<T> frame = new MsClientResponse<>(ResponseCode.SERVER_ERROR);
			frame.setRequestParams(FrameJsonUtil.toString(params));
			frame.setExceptionInfo(e.getMessage());
			LOGGER.error(serviceId + " - " + url + " 出现异常: " + e.getMessage(), e);
			return frame;
		} finally {
			requestFinally(begin, serviceId, url, params, resResult);
		}
	}

	/**
	 * 发送请求
	 * @param serviceId
	 * @param url
	 * @param params
	 * @param responseClazz
	 * @param responsePageClazz
	 * @return
	 */
	public <T> MsClientResponse<T> requestMsClientJsonBody(String serviceId, String url, Map<String, Object> params, Class<?> responseClazz, Class<?> responsePageClazz) {
		long begin = System.currentTimeMillis();
		String resResult = null;
		// 处理灰度服务的逻辑
		serviceId = handleGray(serviceId, params);
		// 获取访问服务的前缀地址
		//String baseUrl = getRestUrl(serviceId, fallbackUri);
		//ServiceInstance instance = getServiceInstance(serviceId);
		String baseUrl = "http://" + serviceId;
		try {
			HttpHeaders headers = new HttpHeaders();
			headers.add(BizLogData.MS_TRACE_ID, BizLogTraceIdUtil.getTraceId());
			// 请勿轻易改变此提交方式，大部分的情况下，提交方式都是表单提交
			headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
			String paramString = FrameJsonUtil.toString(params);
			@SuppressWarnings({ "rawtypes", "unchecked" })
			HttpEntity requestEntity = new HttpEntity(paramString, headers);
			resResult = restTemplate.postForObject(baseUrl + url, requestEntity, String.class);
			MsClientResponse<T> frame = FrameJsonUtil.toObject(resResult, MsClientResponse.class);
			if (frame.getCode() == ResponseCode.SUCC.getCode() && responseClazz != null) {
				frame.convertBody(responseClazz);
			}
			/*if(ResponseCode.SUCC.getCode() != frame.getCode()) {
				String remark = "接口响应异常!";
				saveOptimizeLog(serviceId, url, params, begin, null, null, resResult, remark);
			}*/
			return frame;
		} catch (Exception e) {
			MsClientResponse<T> frame = new MsClientResponse<>(ResponseCode.SERVER_ERROR);
			frame.setRequestParams(FrameJsonUtil.toString(params));
			frame.setExceptionInfo(e.getMessage());
			LOGGER.error(serviceId + " - " + url + " 出现异常: " + e.getMessage(), e);
			return frame;
		} finally {
			requestFinally(begin, serviceId, url, params, resResult);
		}
	}

	/**
	 * 上传文件
	 * @param serviceId
	 * @param url
	 * @param file
	 * @param params
	 * @param responseClazz
	 * @return
	 */
	public <T> MsClientResponse<T> uploadMsClient(String serviceId, String url, File file, Map<String, Object> params, Class<?> responseClazz) {
		long begin = System.currentTimeMillis();
		String resResult = null;
		// 处理灰度服务的逻辑
		serviceId = handleGray(serviceId, params);
		// 获取访问服务的前缀地址
		//String baseUrl = getRestUrl(serviceId, fallbackUri);
		//ServiceInstance instance = getServiceInstance(serviceId);
		String baseUrl = "http://" + serviceId;
		try {
			//设置请求头
			HttpHeaders headers = new HttpHeaders();
			headers.add(BizLogData.MS_TRACE_ID, BizLogTraceIdUtil.getTraceId());
			// 请勿轻易改变此提交方式，大部分的情况下，提交方式都是表单提交
			headers.setContentType(MediaType.MULTIPART_FORM_DATA);
			//设置请求体，注意是LinkedMultiValueMap
			FileSystemResource fileSystemResource = new FileSystemResource(file);
			MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
			form.add("file", fileSystemResource);

			Iterator<Entry<String, Object>> entryKeyIterator = params.entrySet().iterator();
			while (entryKeyIterator.hasNext()) {
				Entry<String, Object> e = entryKeyIterator.next();
				String value = null;
				if(e.getValue() != null) {
					value = e.getValue().toString();
				}
				form.add(e.getKey(), value);
			}

			//用HttpEntity封装整个请求报文
			HttpEntity<MultiValueMap<String, Object>> files = new HttpEntity<>(form, headers);
			String result = restTemplate.postForObject(baseUrl + url, files, String.class);
			MsClientResponse<T> frame = FrameJsonUtil.toObject(result, MsClientResponse.class);
			if (frame.getCode() == ResponseCode.SUCC.getCode() && responseClazz != null) {
				frame.convertBody(responseClazz);
			}
			/*if(ResponseCode.SUCC.getCode() != frame.getCode()) {
				String remark = "接口响应异常!";
				saveOptimizeLog(serviceId, url, params, begin, null, null, resResult, remark);
			}*/
			return frame;
		} catch (Exception e) {
			MsClientResponse<T> frame = new MsClientResponse<>(ResponseCode.SERVER_ERROR);
			frame.setRequestParams(FrameJsonUtil.toString(params));
			frame.setExceptionInfo(e.getMessage());
			LOGGER.error(serviceId + " - " + url + " 出现异常: " + e.getMessage(), e);
			return frame;
		} finally {
			requestFinally(begin, serviceId, url, params, resResult);
		}
	}


	/**
	 * 下载文件
	 * @param serviceId
	 * @param url
	 * @param savePath
	 * @param params
	 * @return
	 */
	public ResponseEntity<byte[]> downloadMsClient(String serviceId, String url, Map<String, Object> params) {
		long begin = System.currentTimeMillis();
		String resResult = null;
		// 处理灰度服务的逻辑
		serviceId = handleGray(serviceId, params);
		// 获取访问服务的前缀地址
		//String baseUrl = getRestUrl(serviceId, fallbackUri);
		//ServiceInstance instance = getServiceInstance(serviceId);
		String baseUrl = "http://" + serviceId;
		try {
			HttpHeaders headers = new HttpHeaders();
			headers.add(BizLogData.MS_TRACE_ID, BizLogTraceIdUtil.getTraceId());
			List<MediaType> list = new ArrayList<MediaType>();
			// 指定下载文件类型
			list.add(MediaType.APPLICATION_OCTET_STREAM);
			headers.setAccept(list);

			// 封装参数，千万不要替换为Map与HashMap，否则参数无法传递
			MultiValueMap<String, String> paramsMvm = new LinkedMultiValueMap<String, String>();
			Iterator<Entry<String, Object>> entryKeyIterator = params.entrySet().iterator();
			while (entryKeyIterator.hasNext()) {
				Entry<String, Object> e = entryKeyIterator.next();
				String value = null;
				if(e.getValue() != null) {
					value = e.getValue().toString();
				}
				paramsMvm.add(e.getKey(), value);
			}
			HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<MultiValueMap<String, String>>(paramsMvm, headers);
			ResponseEntity<byte[]> response = this.restTemplate.exchange(baseUrl + url, HttpMethod.POST, requestEntity, byte[].class);
			return response;
		} catch (Exception e) {
			LOGGER.error(serviceId + " - " + url + " 出现异常: " + e.getMessage(), e);
			requestFinally(begin, serviceId, url, params, resResult);
			return null;
		} finally {
			requestFinally(begin, serviceId, url, params, resResult);
		}
	}

	/**
	 * 处理灰度服务
	 * @param serviceId
	 * @param params
	 * @return
	 */
	private String handleGray(String serviceId, Map<String, Object> params) {
		// 获取灰度用户ID
		String msGuId = FrameMapUtil.getString(params, "msGuId");
		if (FrameStringUtil.isNotEmpty(msGuId)) {
			Gray gray = GrayUtil.get(serviceId);
			if (gray == null) {
				LOGGER.info("灰度用户ID[" + msGuId + "]访问的服务[" + serviceId + "]不存在灰度服务");
			} else {
				// 访问灰度服务
				List<GrayUser> users = gray.getUsers();
				if (users != null && users.size() > 0) {
					for (GrayUser gu : users) {
						if("10".equals(gu.getType())) {
							// 为精确的用户编号
							if (msGuId.equals(gu.getTypeValue())) {
								// 切换为灰度服务
								LOGGER.info("灰度用户ID[" + msGuId + "]访问的服务[" + serviceId + "]切换到灰度服务[" +  gray.getGrayServiceId() + "]");
								serviceId = gray.getGrayServiceId();
							}
						}
					}
				}
			}
		}
		return serviceId;
	}

	private void requestFinally(long begin, String serviceId, String url, Map<String, Object> params, String resResult) {
		long end = System.currentTimeMillis();
		long time = end - begin;
		if(time > 2000) {
			//接口请求>2s，则发送优化信息
			if (url.contains("/api/prjOptimizeLog/save")) {
				// 记录日志的接口不做统计
				return;
			}
			String remark = "请求超时!";
			saveOptimizeLog(serviceId, url, params, begin, end, time, resResult, remark);
		}
	}

	private void saveOptimizeLog(final String serviceId, final String url, final Map<String, Object> params,
			long begin, Long end, Long time, final String resResult, final String remark) {
		if(end == null) {
			end = System.currentTimeMillis();
		}
		if(time == null) {
			time = end - begin;
		}
		/*final Date reqTime = new Date(begin);
		final Date resTime = new Date(end);
		final int useTime = time.intValue();
		ThreadPoolTaskExecutor pool = FrameSpringBeanUtil.getBean(ThreadPoolTaskExecutor.class);
		pool.execute(new Runnable() {
			@Override
			public void run() {
				ApiPrjOptimizeLogService apiPrjOptimizeLogService = FrameSpringBeanUtil.getBean(ApiPrjOptimizeLogService.class);
				String code = serviceId;
				apiPrjOptimizeLogService.save(code, url, FrameJsonUtil.toString(params),
						reqTime, resTime, useTime, resResult, remark);
			}
		});*/
	}
}